# See LICENSE for licensing information.
#
# Copyright (c) 2016-2024 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import sys
import os
import re
from math import sqrt
from openram import debug
from openram.gdsMill import gdsMill
from openram import tech
from openram.tech import drc, GDS
from openram.tech import layer as tech_layer
from openram.tech import layer_indices as tech_layer_indices
from openram.tech import preferred_directions
from openram.tech import layer_stacks as tech_layer_stacks
from openram.tech import active_stack as tech_active_stack
from openram.sram_factory import factory
from openram import OPTS
from .vector import vector
from .pin_layout import pin_layout
from .utils import round_to_grid, ceil
from . import geometry

try:
    from openram.tech import special_purposes
except ImportError:
    special_purposes = {}


class layout():
    """
    Class consisting of a set of objs and instances for a module
    This provides a set of useful generic types for hierarchy
    management. If a module is a custom designed cell, it will read from
    the GDS and spice files and perform LVS/DRC. If it is dynamically
    generated, it should implement a constructor to create the
    layout/netlist and perform LVS/DRC.
    """

    def __init__(self, name, cell_name):
        # This gets set in both spice and layout so either can be called first.
        self.name = name
        self.cell_name = cell_name

        self.gds_file = OPTS.openram_tech + "gds_lib/" + cell_name + ".gds"
        self.is_library_cell = os.path.isfile(self.gds_file)

        self.width = None
        self.height = None
        self.bounding_box = None # The rectangle shape
        self.bbox = None # The ll, ur coords
        # Holds module/cell layout instances
        self.insts = []
        # Set of names to check for duplicates
        self.inst_names = set()
        # Holds all other objects (labels, geometries, etc)
        self.objs = []
        # This is a mapping of internal pin names to cell pin names
        # If the key is not found, the internal pin names is assumed
        self.pin_names = {}
        # Holds name->pin_layout map for all pins
        self.pin_map = {}
        # List of modules we have already visited
        self.visited = []

        self.gds_read()

        if "contact" not in self.name:
            if not hasattr(layout, "_drc_constants"):
                layout._drc_constants = True
                layout.setup_drc_constants()
                layout.setup_contacts()
                layout.setup_layer_constants()


    @classmethod
    def setup_drc_constants(layout):
        """
        These are some DRC constants used in many places
        in the compiler.
        """

        # Make some local rules for convenience
        for rule in drc.keys():
            # Single layer width rules
            match = re.search(r"minwidth_(.*)", rule)
            if match:
                if match.group(1) == "active_contact":
                    setattr(layout, "contact_width", drc(match.group(0)))
                else:
                    setattr(layout, match.group(1) + "_width", drc(match.group(0)))

            # Single layer area rules
            match = re.search(r"minarea_(.*)", rule)
            if match:
                setattr(layout, match.group(0), drc(match.group(0)))

            # Single layer spacing rules
            match = re.search(r"(.*)_to_(.*)", rule)
            if match and match.group(1) == match.group(2):
                setattr(layout, match.group(1) + "_space", drc(match.group(0)))
            elif match and match.group(1) != match.group(2):
                if match.group(2) == "poly_active":
                    setattr(layout, match.group(1) + "_to_contact",
                            drc(match.group(0)))
                else:
                    setattr(layout, match.group(0), drc(match.group(0)))

            match = re.search(r"(.*)_enclose_(.*)", rule)
            if match:
                setattr(layout, match.group(0), drc(match.group(0)))

            match = re.search(r"(.*)_extend_(.*)", rule)
            if match:
                setattr(layout, match.group(0), drc(match.group(0)))

        # Create the maximum well extend active that gets used
        # by cells to extend the wells for interaction with other cells
        layout.well_extend_active = 0
        if "nwell" in tech_layer:
            layout.well_extend_active = max(layout.well_extend_active, layout.nwell_extend_active)
        if "pwell" in tech_layer:
            layout.well_extend_active = max(layout.well_extend_active, layout.pwell_extend_active)

        # The active offset is due to the well extension
        if "pwell" in tech_layer:
            layout.pwell_enclose_active = drc("pwell_enclose_active")
        else:
            layout.pwell_enclose_active = 0
        if "nwell" in tech_layer:
            layout.nwell_enclose_active = drc("nwell_enclose_active")
        else:
            layout.nwell_enclose_active = 0
        # Use the max of either so that the poly gates will align properly
        layout.well_enclose_active = max(layout.pwell_enclose_active,
                                       layout.nwell_enclose_active,
                                       layout.active_space)

        # These are for debugging previous manual rules
        level=99
        debug.info(level, "poly_width".format(layout.poly_width))
        debug.info(level, "poly_space".format(layout.poly_space))
        debug.info(level, "m1_width".format(layout.m1_width))
        debug.info(level, "m1_space".format(layout.m1_space))
        debug.info(level, "m2_width".format(layout.m2_width))
        debug.info(level, "m2_space".format(layout.m2_space))
        debug.info(level, "m3_width".format(layout.m3_width))
        debug.info(level, "m3_space".format(layout.m3_space))
        debug.info(level, "m4_width".format(layout.m4_width))
        debug.info(level, "m4_space".format(layout.m4_space))
        debug.info(level, "active_width".format(layout.active_width))
        debug.info(level, "active_space".format(layout.active_space))
        debug.info(level, "contact_width".format(layout.contact_width))
        debug.info(level, "poly_to_active".format(layout.poly_to_active))
        debug.info(level, "poly_extend_active".format(layout.poly_extend_active))
        debug.info(level, "poly_to_contact".format(layout.poly_to_contact))
        debug.info(level, "active_contact_to_gate".format(layout.active_contact_to_gate))
        debug.info(level, "poly_contact_to_gate".format(layout.poly_contact_to_gate))
        debug.info(level, "well_enclose_active".format(layout.well_enclose_active))
        debug.info(level, "implant_enclose_active".format(layout.implant_enclose_active))
        debug.info(level, "implant_space".format(layout.implant_space))

    @classmethod
    def setup_layer_constants(layout):
        """
        These are some layer constants used
        in many places in the compiler.
        """
        try:
            from openram.tech import power_grid
            layout.pwr_grid_layers = [power_grid[0], power_grid[2]]
        except ImportError:
            layout.pwr_grid_layers = ["m3", "m4"]

        for layer_id in tech_layer_indices:
            key = "{}_stack".format(layer_id)

            # Set the stack as a local helper
            try:
                layer_stack = getattr(tech, key)
                setattr(layout, key, layer_stack)
            except AttributeError:
                pass

            # Skip computing the pitch for non-routing layers
            if layer_id in ["active", "nwell", "pwell"]:
                continue

            # Add the pitch
            setattr(layout,
                    "{}_pitch".format(layer_id),
                    layout.compute_pitch(layer_id, True))

            # Add the non-preferrd pitch (which has vias in the "wrong" way)
            setattr(layout,
                    "{}_nonpref_pitch".format(layer_id),
                    layout.compute_pitch(layer_id, False))

        level=99
        for name in tech_layer_indices:
            if name == "active":
                continue
            try:
                debug.info(level, "{0} width {1} space {2}".format(name,
                                                       getattr(layout, "{}_width".format(name)),
                                                       getattr(layout, "{}_space".format(name))))

                debug.info(level, "pitch {0} nonpref {1}".format(getattr(layout, "{}_pitch".format(name)),
                                                     getattr(layout, "{}_nonpref_pitch".format(name))))
            except AttributeError:
                pass

    @staticmethod
    def compute_pitch(layer, preferred=True):
        """
        This is the preferred direction pitch
        i.e. we take the minimum or maximum contact dimension
        """
        # Find the layer stacks this is used in
        pitches = []
        for stack in tech_layer_stacks:
            # Compute the pitch with both vias above and below (if they exist)
            if stack[0] == layer:
                pitches.append(layout.compute_layer_pitch(stack, preferred))
            if stack[2] == layer:
                pitches.append(layout.compute_layer_pitch(stack[::-1], True))

        return max(pitches)

    @staticmethod
    def get_preferred_direction(layer):
        return preferred_directions[layer]

    @staticmethod
    def compute_layer_pitch(layer_stack, preferred):

        (layer1, via, layer2) = layer_stack
        try:
            if layer1 == "poly" or layer1 == "active":
                contact1 = getattr(layout, layer1 + "_contact")
            else:
                contact1 = getattr(layout, layer1 + "_via")
        except AttributeError:
            contact1 = getattr(layout, layer2 + "_via")

        if preferred:
            if preferred_directions[layer1] == "V":
                contact_width = contact1.first_layer_width
            else:
                contact_width = contact1.first_layer_height
        else:
            if preferred_directions[layer1] == "V":
                contact_width = contact1.first_layer_height
            else:
                contact_width = contact1.first_layer_width
        layer_space = getattr(layout, layer1 + "_space")


        pitch = contact_width + layer_space

        return round_to_grid(pitch)


    @classmethod
    def setup_contacts(layout):
        # Set up a static for each layer to be used for measurements
        # unless we are a contact class!

        for layer_stack in tech_layer_stacks:
            (layer1, via, layer2) = layer_stack
            cont = factory.create(module_type="contact",
                                  layer_stack=layer_stack)
            module = sys.modules[__name__]
            # Also create a contact that is just the first layer
            if layer1 == "poly" or layer1 == "active":
                setattr(layout, layer1 + "_contact", cont)
            else:
                setattr(layout, layer1 + "_via", cont)

        # Set up a static for each well contact for measurements
        if "nwell" in tech_layer:
            cont = factory.create(module_type="contact",
                                  layer_stack=tech_active_stack,
                                  implant_type="n",
                                  well_type="n")
            module = sys.modules[__name__]
            setattr(layout, "nwell_contact", cont)

        if "pwell" in tech_layer:
            cont = factory.create(module_type="contact",
                                  layer_stack=tech_active_stack,
                                  implant_type="p",
                                  well_type="p")
            module = sys.modules[__name__]
            setattr(layout, "pwell_contact", cont)



    ############################################################
    # GDS layout
    ############################################################
    def offset_all_coordinates(self, offset=None):
        """
        This function is called after everything is placed to
        shift the origin in the lowest left corner
        """
        if not offset:
            offset = vector(0, 0)
        ll = self.find_lowest_coords()
        real_offset = ll + offset
        self.translate_all(real_offset)
        return real_offset

    def offset_x_coordinates(self, offset=None):
        """
        This function is called after everything is placed to
        shift the origin to the furthest left point.
        Y offset is unchanged.
        """
        if not offset:
            offset = vector(0, 0)
        ll = self.find_lowest_coords()
        real_offset = ll.scale(1, 0) + offset
        self.translate_all(real_offset)
        return real_offset

    def get_gate_offset(self, x_offset, height, inv_num):
        """
        Gets the base offset and y orientation of stacked rows of gates
        assuming a minwidth metal1 vdd/gnd rail. Input is which gate
        in the stack from 0..n
        """

        if (inv_num % 2 == 0):
            base_offset = vector(x_offset, inv_num * height)
            y_dir = 1
        else:
            # we lose a rail after every 2 gates
            base_offset = vector(x_offset,
                                 (inv_num + 1) * height - \
                                 (inv_num % 2) * drc["minwidth_m1"])
            y_dir = -1

        return (base_offset, y_dir)

    def find_lowest_coords(self):
        """
        Finds the lowest set of 2d cartesian coordinates within
        this layout
        """
        lowestx = lowesty = sys.maxsize

        if len(self.objs) > 0:
            lowestx = min(min(obj.lx() for obj in self.objs if obj.name != "label"), lowestx)
            lowesty = min(min(obj.by() for obj in self.objs if obj.name != "label"), lowesty)

        if len(self.insts) > 0:
            lowestx = min(min(inst.lx() for inst in self.insts), lowestx)
            lowesty = min(min(inst.by() for inst in self.insts), lowesty)

        if len(self.pin_map) > 0:
            for pin_set in self.pin_map.values():
                if len(pin_set) == 0:
                    continue
                lowestx = min(min(pin.lx() for pin in pin_set), lowestx)
                lowesty = min(min(pin.by() for pin in pin_set), lowesty)

        return vector(lowestx, lowesty)

    def find_highest_coords(self):
        """
        Finds the highest set of 2d cartesian coordinates within
        this layout
        """
        highestx = highesty = -sys.maxsize - 1

        if len(self.objs) > 0:
            highestx = max(max(obj.rx() for obj in self.objs if obj.name != "label"), highestx)
            highesty = max(max(obj.uy() for obj in self.objs if obj.name != "label"), highesty)

        if len(self.insts) > 0:
            highestx = max(max(inst.rx() for inst in self.insts), highestx)
            highesty = max(max(inst.uy() for inst in self.insts), highesty)

        if len(self.pin_map) > 0:
            for pin_set in self.pin_map.values():
                if len(pin_set) == 0:
                    continue
                highestx = max(max(pin.rx() for pin in pin_set), highestx)
                highesty = max(max(pin.uy() for pin in pin_set), highesty)

        return vector(highestx, highesty)

    def find_highest_layer_coords(self, layer):
        """
        Finds the highest set of 2d cartesian coordinates within
        this layout on a layer
        """
        # Only consider the layer not the purpose for now
        layerNumber = tech_layer[layer][0]
        try:
            highestx = max(obj.rx() for obj in self.objs if obj.layerNumber == layerNumber)
        except ValueError:
            highestx =0
        try:
            highesty = max(obj.uy() for obj in self.objs if obj.layerNumber == layerNumber)
        except ValueError:
            highesty = 0

        for inst in self.insts:
            # This really should be rotated/mirrored etc...
            subcoord = inst.mod.find_highest_layer_coords(layer) + inst.offset
            highestx = max(highestx, subcoord.x)
            highesty = max(highesty, subcoord.y)

        return vector(highestx, highesty)

    def find_lowest_layer_coords(self, layer):
        """
        Finds the highest set of 2d cartesian coordinates within
        this layout on a layer
        """
        # Only consider the layer not the purpose for now
        layerNumber = tech_layer[layer][0]
        try:
            lowestx = min(obj.lx() for obj in self.objs if obj.layerNumber == layerNumber)
        except ValueError:
            lowestx = 0
        try:
            lowesty = min(obj.by() for obj in self.objs if obj.layerNumber == layerNumber)
        except ValueError:
            lowesty = 0

        for inst in self.insts:
            # This really should be rotated/mirrored etc...
            subcoord = inst.mod.find_lowest_layer_coords(layer) + inst.offset
            lowestx = min(lowestx, subcoord.x)
            lowesty = min(lowesty, subcoord.y)

        return vector(lowestx, lowesty)

    def translate_all(self, offset):
        """
        Translates all objects, instances, and pins by the given (x,y) offset
        """
        for obj in self.objs:
            obj.offset = vector(obj.offset - offset)
        for inst in self.insts:
            inst.offset = vector(inst.offset - offset)
            # The instances have a precomputed boundary that we need to update.
            if inst.__class__.__name__ == "instance":
                inst.compute_boundary(inst.offset, inst.mirror, inst.rotate)
        for pin_name in self.pin_map.keys():
            # All the pins are absolute coordinates that need to be updated.
            pin_list = self.pin_map[pin_name]
            for pin in pin_list:
                pin.rect = [pin.ll() - offset, pin.ur() - offset]

    def add_inst(self, name, mod, offset=[0, 0], mirror="R0", rotate=0):
        """ Adds an instance of a mod to this module """
        # Contacts are not really instances, so skip them
        if "contact" not in mod.name:
            # Check that the instance name is unique
            debug.check(name not in self.inst_names, "Duplicate named instance in {0}: {1}".format(self.cell_name, name))

        self.mods.add(mod)
        self.inst_names.add(name)
        self.insts.append(geometry.instance(name, mod, offset, mirror, rotate))
        debug.info(3, "adding instance {}".format(self.insts[-1]))
        # This is commented out for runtime reasons
        # debug.info(4, "instance list: " + ",".join(x.name for x in self.insts))
        return self.insts[-1]

    def get_inst(self, name):
        """ Retrieve an instance by name """
        for inst in self.insts:
            if inst.name == name:
                return inst
        return None

    def add_flat_inst(self, name, mod, offset=[0, 0]):
        """ Copies all of the items in instance into this module """
        for item in mod.objs:
            item.offset += offset
            self.objs.append(item)
        for item in mod.insts:
            item.offset += offset
            self.insts.append(item)
            debug.check(len(item.mod.pins) == 0, "Cannot add flat instance with subinstances.")
            self.connect_inst([])
        debug.info(3, "adding flat instance {}".format(name))
        return None

    def add_rect(self, layer, offset, width=None, height=None):
        """
        Adds a rectangle on a given layer,offset with width and height
        """
        if not width:
            width = drc["minwidth_{}".format(layer)]
        if not height:
            height = drc["minwidth_{}".format(layer)]
        lpp = tech_layer[layer]
        self.objs.append(geometry.rectangle(lpp,
                                            offset,
                                            width,
                                            height))
        return self.objs[-1]

    def add_rect_center(self, layer, offset, width=None, height=None):
        """
        Adds a rectangle on a given layer at the center
        point with width and height
        """
        if not width:
            width = drc["minwidth_{}".format(layer)]
        if not height:
            height = drc["minwidth_{}".format(layer)]
        lpp = tech_layer[layer]
        corrected_offset = offset - vector(0.5 * width, 0.5 * height)
        self.objs.append(geometry.rectangle(lpp,
                                            corrected_offset,
                                            width,
                                            height))
        return self.objs[-1]

    def add_segment_center(self, layer, start, end, width=None):
        """
        Add a min-width rectanglular segment using center
        line on the start to end point
        """
        if not width:
            width = drc["minwidth_{}".format(layer)]

        if start.x != end.x and start.y != end.y:
            debug.error("Nonrectilinear center rect!", -1)
        elif start.x != end.x:
            offset = vector(0, 0.5 * width)
            return self.add_rect(layer,
                                 start - offset,
                                 end.x - start.x,
                                 width)
        else:
            offset = vector(0.5 * width, 0)
            return self.add_rect(layer,
                                 start - offset,
                                 width,
                                 end.y - start.y)

    def get_tx_insts(self, tx_type=None):
        """
        Return a list of the instances of given tx type.
        """
        tx_list = []
        for i in self.insts:
            try:
                if tx_type and i.mod.tx_type == tx_type:
                    tx_list.append(i)
                elif not tx_type:
                    if i.mod.tx_type == "nmos" or i.mod.tx_type == "pmos":
                        tx_list.append(i)
            except AttributeError:
                pass

        return tx_list

    def get_pin(self, text):
        """
        Return the pin or list of pins
        """
        name = self.get_pin_name(text)

        try:
            if len(self.pin_map[name]) > 1:
                debug.error("Should use a pin iterator since more than one pin {}".format(text), -1)
            # If we have one pin, return it and not the list.
            # Otherwise, should use get_pins()
            any_pin = next(iter(self.pin_map[name]))
            return any_pin
        except Exception:
            self.gds_write("missing_pin.gds")
            debug.error("No pin found with name {0} on {1}. Saved as missing_pin.gds.".format(name, self.cell_name), -1)

    def get_pins(self, text):
        """
        Return a pin list (instead of a single pin)
        """
        name = self.get_pin_name(text)

        if name in self.pin_map.keys():
            return self.pin_map[name]
        else:
            return set()

    def add_pin_names(self, pin_dict):
        """
        Create a mapping from internal pin names to external pin names.
        """
        self.pin_names = pin_dict

        self.original_pin_names = {y: x for (x, y) in self.pin_names.items()}

    def get_pin_name(self, text):
        """ Return the custom cell pin name """

        if text in self.pin_names:
            return self.pin_names[text]
        else:
            return text

    def get_original_pin_names(self):
        """ Return the internal cell pin name """

        # This uses the hierarchy_spice pins (in order)
        return [self.get_original_pin_name(x) for x in self.pins]

    def get_original_pin_name(self, text):
        """ Return the internal cell pin names in custom port order """
        if text in self.original_pin_names:
            return self.original_pin_names[text]
        else:
            return text

    def get_pin_names(self):
        """
        Return a pin list of all pins
        """
        return list(self.pins)

    def copy_layout_pin(self, instance, pin_name, new_name="", relative_offset=vector(0, 0)):
        """
        Create a copied version of the layout pin at the current level.
        You can optionally rename the pin to a new name.
        You can optionally add an offset vector by which to move the pin.
        """
        pins = instance.get_pins(pin_name)

        if len(pins) == 0:
            debug.warning("Could not find pin {0} on {1}".format(pin_name, instance.mod.name))

        for pin in pins:
            if new_name == "":
                new_name = pin_name
            self.add_layout_pin(new_name,
                                pin.layer,
                                pin.ll() + relative_offset,
                                pin.width(),
                                pin.height())


    def connect_row_locs(self, from_layer, to_layer, locs, name=None, full=False):
        """
        Connects left/right rows that are aligned on the given layer.
        """
        bins = {}
        for loc in locs:
                y = pin.y
                try:
                    bins[y].append(loc)
                except KeyError:
                    bins[y] = [loc]

        for y, v in bins.items():
            # Not enough to route a pin, so just copy them
            if len(v) < 2:
                continue

            if full:
                left_x = 0
                right_x = self.width
            else:
                left_x = min([loc.x for loc in v])
                right_x = max([loc.x for loc in v])

            left_pos = vector(left_x, y)
            right_pos = vector(right_x, y)

            # Make sure to add vias to the new route
            for loc in v:
                self.add_via_stack_center(from_layer=from_layer,
                                          to_layer=to_layer,
                                          offset=loc,
                                          min_area=True)

            if name:
                self.add_layout_pin_segment_center(text=name,
                                                   layer=to_layer,
                                                   start=left_pos,
                                                   end=right_pos)
            else:
                self.add_segment_center(layer=to_layer,
                                        start=left_pos,
                                        end=right_pos)

    def connect_row_pins(self, layer, pins, name=None, full=False, round=False):
        """
        Connects left/right rows that are aligned.
        """
        bins = {}
        for pin in pins:
                y = pin.cy()
                if round:
                    y = round_to_grid(y)
                try:
                    bins[y].append(pin)
                except KeyError:
                    bins[y] = [pin]

        for y, v in bins.items():
            # Not enough to route a pin, so just copy them
            if len(v) < 2:
                continue

            if full:
                left_x = 0
                right_x = self.width
            else:
                left_x = min([pin.lx() for pin in v])
                right_x = max([pin.rx() for pin in v])

            left_pos = vector(left_x, y)
            right_pos = vector(right_x, y)

            # Make sure to add vias to the new route
            for pin in v:
                self.add_via_stack_center(from_layer=pin.layer,
                                          to_layer=layer,
                                          offset=pin.center(),
                                          min_area=True)

            if name:
                self.add_layout_pin_segment_center(text=name,
                                                   layer=layer,
                                                   start=left_pos,
                                                   end=right_pos)
            else:
                self.add_segment_center(layer=layer,
                                        start=left_pos,
                                        end=right_pos)

    def connect_col_locs(self, from_layer, to_layer, locs, name=None, full=False):
        """
        Connects top/bot columns that are aligned.
        """
        bins = {}
        for loc in locs:
                x = loc.x
                try:
                    bins[x].append(loc)
                except KeyError:
                    bins[x] = [loc]

        for x, v in bins.items():
            # Not enough to route a pin, so just copy them
            if len(v) < 2:
                continue

            if full:
                bot_y = 0
                top_y = self.height
            else:
                bot_y = min([loc.y for loc in v])
                top_y = max([loc.y for loc in v])

            top_pos = vector(x, top_y)
            bot_pos = vector(x, bot_y)

            # Make sure to add vias to the new route
            for loc in v:
                self.add_via_stack_center(from_layer=from_layer,
                                          to_layer=to_layer,
                                          offset=loc,
                                          min_area=True)

            if name:
                self.add_layout_pin_segment_center(text=name,
                                                   layer=to_layer,
                                                   start=top_pos,
                                                   end=bot_pos)
            else:
                self.add_segment_center(layer=to_layer,
                                        start=top_pos,
                                        end=bot_pos)


    def connect_col_pins(self, layer, pins, name=None, full=False, round=False, directions="pref"):
        """
        Connects top/bot columns that are aligned.
        """
        bins = {}
        for pin in pins:
                x = pin.cx()
                if round:
                    x = round_to_grid(x)
                try:
                    bins[x].append(pin)
                except KeyError:
                    bins[x] = [pin]

        for x, v in bins.items():
            # Not enough to route a pin, so just copy them
            if len(v) < 2:
                continue

            if full:
                bot_y = 0
                top_y = self.height
            else:
                bot_y = min([pin.by() for pin in v])
                top_y = max([pin.uy() for pin in v])

            top_pos = vector(x, top_y)
            bot_pos = vector(x, bot_y)

            # Make sure to add vias to the new route
            for pin in v:
                self.add_via_stack_center(from_layer=pin.layer,
                                          to_layer=layer,
                                          offset=pin.center(),
                                          min_area=True,
                                          directions=directions)

            if name:
                self.add_layout_pin_segment_center(text=name,
                                                   layer=layer,
                                                   start=top_pos,
                                                   end=bot_pos)
            else:
                self.add_segment_center(layer=layer,
                                        start=top_pos,
                                        end=bot_pos)

    def get_metal_layers(self, from_layer, to_layer):

        from_id = tech_layer_indices[from_layer]
        to_id   = tech_layer_indices[to_layer]
        layer_list = [x for x in tech_layer_indices.keys() if tech_layer_indices[x] > from_id and tech_layer_indices[x] < to_id]

        return layer_list


    def route_vertical_pins(self, name, insts=None, layer=None, xside="cx", yside="cy", full_width=True):
        """
        Route together all of the pins of a given name that vertically align.
        Uses local_insts if insts not specified.
        Uses center of pin by default, or right or left if specified.
        TODO: Add equally spaced option for IR drop min, right now just 2
        """


        bins = {}
        if not insts:
            insts = self.local_insts

        for inst in insts:
            for pin in inst.get_pins(name):

                x = getattr(pin, xside)()
                try:
                    bins[x].append((inst,pin))
                except KeyError:
                    bins[x] = [(inst,pin)]

        for x, v in bins.items():
            # Not enough to route a pin, so just copy them
            if len(v) < 2:
                debug.warning("Pins don't align well so copying pins instead of connecting with pin.")
                for inst,pin in v:
                    self.add_layout_pin(pin.name,
                                        pin.layer,
                                        pin.ll(),
                                        pin.width(),
                                        pin.height())
                continue

            last_via = None
            pin_layer = None
            for inst,pin in v:
                if layer:
                    pin_layer = layer
                else:
                    pin_layer = self.supply_stack[2]

                y = getattr(pin, yside)()

                last_via = self.add_via_stack_center(from_layer=pin.layer,
                                                     to_layer=pin_layer,
                                                     offset=vector(x, y))

            if last_via:
                via_width=last_via.mod.second_layer_width
                via_height=last_via.mod.second_layer_height
            else:
                via_width=None
                via_height=0

            bot_y = min([pin.by() for (inst,pin) in v])
            top_y = max([pin.uy() for (inst,pin) in v])

            if full_width:
                bot_y = min(0, bot_y)
                top_y = max(self.height, top_y)

            top_pos = vector(x, top_y + 0.5 * via_height)
            bot_pos = vector(x, bot_y - 0.5 * via_height)

#            self.add_layout_pin_rect_ends(name=name,
#                                          layer=pin_layer,
#                                          start=top_pos,
#                                          end=bot_pos,
#                                          width=via_width)
            self.add_layout_pin_segment_center(text=name,
                                               layer=pin_layer,
                                               start=top_pos,
                                               end=bot_pos,
                                               width=via_width)


    def add_layout_pin_rect_ends(self, name, layer, start, end, width=None):

        # This adds pins on the end connected by a segment
        top_rect = self.add_layout_pin_rect_center(text=name,
                                                   layer=layer,
                                                   offset=start)
        bot_rect = self.add_layout_pin_rect_center(text=name,
                                                   layer=layer,
                                                   offset=end)
        # This is made to not overlap with the pin above
        # so that the power router will only select a small pin.
        # Otherwise it adds big blockages over the rails.
        if start.y != end.y:
            self.add_segment_center(layer=layer,
                                    start=bot_rect.uc(),
                                    end=top_rect.bc())
        else:
            self.add_segment_center(layer=layer,
                                    start=bot_rect.rc(),
                                    end=top_rect.lc())

        return (bot_rect, top_rect)

    def route_horizontal_pins(self, name, insts=None, layer=None, xside="cx", yside="cy", full_width=True, new_name=None):
        """
        Route together all of the pins of a given name that horizontally align.
        Uses local_insts if insts not specified.
        Uses center of pin by default, or top or botom if specified.
        New top level pin can be renamed with new_name, otherwise the new pin will keep the same name as old pins
        TODO: Add equally spaced option for IR drop min, right now just 2
        """

        if new_name is not None:
            pin_name = new_name
        else:
            pin_name = name

        bins = {}
        if not insts:
            insts = self.local_insts

        for inst in insts:
            for pin in inst.get_pins(name):

                y = getattr(pin, yside)()
                try:
                    bins[y].append((inst,pin))
                except KeyError:
                    bins[y] = [(inst,pin)]

        # Filter the small bins

        for y, v in bins.items():
            if len(v) < 2:
                debug.warning("Pins don't align well so copying pins instead of connecting with pin.")
                for inst,pin in v:
                    self.add_layout_pin(pin.name,
                                        pin.layer,
                                        pin.ll(),
                                        pin.width(),
                                        pin.height())
                continue

            last_via = None
            pin_layer = None
            for inst,pin in v:
                if layer:
                    pin_layer = layer
                else:
                    pin_layer = self.supply_stack[0]

                x = getattr(pin, xside)()

                last_via = self.add_via_stack_center(from_layer=pin.layer,
                                                     to_layer=pin_layer,
                                                     offset=vector(x, y),
                                                     min_area=True)

            if last_via:
                via_height=last_via.mod.second_layer_height
                via_width=last_via.mod.second_layer_width
            else:
                via_height=None
                via_width=0

            left_x = min([pin.lx() for (inst,pin) in v])
            right_x = max([pin.rx() for (inst,pin) in v])

            if full_width:
                left_x = min(0, left_x)
                right_x = max(self.width, right_x)

            left_pos = vector(left_x + 0.5 * via_width, y)
            right_pos = vector(right_x + 0.5 * via_width, y)

            # self.add_layout_pin_rect_ends(name=name,
            #                                 layer=pin_layer,
            #                                 start=left_pos,
            #                                 end=right_pos,
            #                                 width=via_height)

            self.add_layout_pin_segment_center(text=pin_name,
                                            layer=pin_layer,
                                            start=left_pos,
                                            end=right_pos,
                                            width=via_height)

    def add_layout_end_pin_segment_center(self, text, layer, start, end):
        """
        Creates a path with two pins on the end that don't overlap.
        """

        start_pin = self.add_layout_pin_rect_center(text=text,
                                                    layer=layer,
                                                    offset=start)
        end_pin = self.add_layout_pin_rect_center(text=text,
                                                  layer=layer,
                                                  offset=end)

        if start.x != end.x and start.y != end.y:
            file_name = "non_rectilinear.gds"
            self.gds_write(file_name)
            debug.error("Cannot have a non-manhatten layout pin: {}".format(file_name), -1)
        elif start.x != end.x:
            self.add_segment_center(layer=layer,
                                    start=start_pin.rc(),
                                    end=end_pin.lc())
        elif start.y != end.y:
            self.add_segment_center(layer=layer,
                                    start=start_pin.uc(),
                                    end=end_pin.bc())
        else:
            debug.error("Cannot have a point pin.", -1)

    def add_layout_pin_segment_center(self, text, layer, start, end, width=None):
        """
        Creates a path like pin with center-line convention
        """
        if start.x != end.x and start.y != end.y:
            file_name = "non_rectilinear.gds"
            self.gds_write(file_name)
            debug.error("Cannot have a non-manhatten layout pin: {}".format(file_name), -1)

        if not width:
            layer_width = drc["minwidth_{}".format(layer)]
        else:
            layer_width = width

        # one of these will be zero
        bbox_width = max(start.x, end.x) - min(start.x, end.x)
        bbox_height = max(start.y, end.y) - min(start.y, end.y)
        ll_offset = vector(min(start.x, end.x), min(start.y, end.y))

        # Shift it down 1/2 a width in the 0 dimension
        if bbox_height == 0:
            ll_offset -= vector(0, 0.5 * layer_width)
        if bbox_width == 0:
            ll_offset -= vector(0.5 * layer_width, 0)

        return self.add_layout_pin(text=text,
                                   layer=layer,
                                   offset=ll_offset,
                                   width=max(bbox_width, layer_width),
                                   height=max(bbox_height, layer_width))

    def add_layout_pin_rect_center(self, text, layer, offset, width=None, height=None):
        """ Creates a path like pin with center-line convention """
        if not width:
            width = drc["minwidth_{0}".format(layer)]
        if not height:
            height = drc["minwidth_{0}".format(layer)]

        ll_offset = offset - vector(0.5 * width, 0.5 * height)

        return self.add_layout_pin(text, layer, ll_offset, width, height)

    def remove_layout_pin(self, text):
        """
        Delete a labeled pin (or all pins of the same name)
        """
        self.pin_map[text] = set()

    def remove_layout_pins(self):
        """
        Delete all the layout pins
        """
        self.pin_map = {}

    def copy_layout_pin_shapes(self, text):
        """
        Copy the shapes of the layout pins as objects.
        """
        for s in self.pin_map[text]:
            self.add_rect(layer=s.layer,
                          offset=s.ll(),
                          width=s.width(),
                          height=s.height())

    def replace_layout_pin(self, text, pin):
        """
        Remove the old pin and replace with a new one
        """
        # Keep the shapes as they were used to connect to the router pins
        self.copy_layout_pin_shapes(text)
        # Remove the shapes as actual pins
        self.remove_layout_pin(text)
        # Add the new pin
        self.add_layout_pin(text=text,
                            layer=pin.layer,
                            offset=pin.ll(),
                            width=pin.width(),
                            height=pin.height())

    def add_layout_pin(self, text, layer, offset, width=None, height=None):
        """
        Create a labeled pin
        """
        if not width:
            width = drc["minwidth_{0}".format(layer)]
        if not height:
            height = drc["minwidth_{0}".format(layer)]

        new_pin = pin_layout(text,
                             [offset, offset + vector(width, height)],
                             layer)

        try:
            # Check if there's a duplicate!
            # and if so, silently ignore it.
            # Rounding errors may result in some duplicates.
            if new_pin not in self.pin_map[text]:
                self.pin_map[text].add(new_pin)
        except KeyError:
            self.pin_map[text] = set()
            self.pin_map[text].add(new_pin)

        return new_pin

    def add_label_pin(self, text, layer, offset, width=None, height=None):
        """
        Create a labeled pin WITHOUT the pin data structure. This is not an
        actual pin but a named net so that we can add a correspondence point
        in LVS.
        """
        if not width:
            width = drc["minwidth_{0}".format(layer)]
        if not height:
            height = drc["minwidth_{0}".format(layer)]
        self.add_rect(layer=layer,
                      offset=offset,
                      width=width,
                      height=height)
        self.add_label(text=text,
                       layer=layer,
                       offset=offset + vector(0.5 * width,
                                              0.5 * height))

    def add_label(self, text, layer, offset=[0, 0], zoom=None):
        """Adds a text label on the given layer,offset, and zoom level"""
        debug.info(5, "add label " + str(text) + " " + layer + " " + str(offset))
        lpp = tech_layer[layer]
        self.objs.append(geometry.label(text, lpp, offset, zoom))
        return self.objs[-1]

    def add_path(self, layer, coordinates, width=None):
        """Connects a routing path on given layer,coordinates,width."""
        debug.info(4, "add path " + str(layer) + " " + str(coordinates))
        from . import wire_path
        # NOTE: (UNTESTED) add_path(...) is currently not used
        # lpp = tech_layer[layer]
        # self.objs.append(geometry.path(lpp, coordinates, width))

        wire_path(obj=self,
                  layer=layer,
                  position_list=coordinates,
                  width=width)

    def add_route(self, layers, coordinates, layer_widths):
        """Connects a routing path on given layer,coordinates,width. The
        layers are the (horizontal, via, vertical). add_wire assumes
        preferred direction routing whereas this includes layers in
        the coordinates.
        """
        from . import route
        debug.info(4, "add route " + str(layers) + " " + str(coordinates))
        # add an instance of our path that breaks down into rectangles and contacts
        route(obj=self,
              layer_stack=layers,
              path=coordinates,
              layer_widths=layer_widths)

    def add_zjog(self, layer, start, end, first_direction="H", var_offset=0.5, fixed_offset=None):
        """
        Add a simple jog at the halfway point.
        If layer is a single value, it is a path.
        If layer is a tuple, it is a wire with preferred directions.
        """

        neg_offset = 1.0 - var_offset
        # vertical first
        if first_direction == "V":
            if fixed_offset:
                mid1 = vector(start.x, fixed_offset)
            else:
                mid1 = vector(start.x, neg_offset * start.y + var_offset * end.y)
            mid2 = vector(end.x, mid1.y)
        # horizontal first
        elif first_direction == "H":
            if fixed_offset:
                mid1 = vector(fixed_offset, start.y)
            else:
                mid1 = vector(neg_offset * start.x + var_offset * end.x, start.y)
            mid2 = vector(mid1, end.y)
        else:
            debug.error("Invalid direction for jog -- must be H or V.")

        if layer in tech_layer_stacks:
            self.add_wire(layer, [start, mid1, mid2, end])
        elif layer in tech_layer:
            self.add_path(layer, [start, mid1, mid2, end])
        else:
            debug.error("Could not find layer {}".format(layer))

    def add_horizontal_zjog_path(self, layer, start, end):
        """ Add a simple jog at the halfway point """

        # horizontal first
        mid1 = vector(0.5 * start.x + 0.5 * end.x, start.y)
        mid2 = vector(mid1, end.y)
        self.add_path(layer, [start, mid1, mid2, end])

    def add_wire(self, layers, coordinates, widen_short_wires=True):
        """Connects a routing path on given layer,coordinates,width.
        The layers are the (horizontal, via, vertical). """
        from . import wire
        # add an instance of our path that breaks down
        # into rectangles and contacts
        wire(obj=self,
             layer_stack=layers,
             position_list=coordinates,
             widen_short_wires=widen_short_wires)

    def add_via(self, layers, offset, size=[1, 1], directions=None, implant_type=None, well_type=None):
        """ Add a three layer via structure. """
        via = factory.create(module_type="contact",
                             layer_stack=layers,
                             dimensions=size,
                             directions=directions,
                             implant_type=implant_type,
                             well_type=well_type)
        inst = self.add_inst(name=via.name,
                             mod=via,
                             offset=offset)
        # We don't model the logical connectivity of wires/paths
        self.connect_inst([])
        return inst

    def add_via_center(self, layers, offset, directions=None, size=[1, 1], implant_type=None, well_type=None):
        """
        Add a three layer via structure by the center coordinate
        accounting for mirroring and rotation.
        """
        via = factory.create(module_type="contact",
                             layer_stack=layers,
                             dimensions=size,
                             directions=directions,
                             implant_type=implant_type,
                             well_type=well_type)
        height = via.height
        width = via.width

        corrected_offset = offset + vector(-0.5 * width,
                                           -0.5 * height)

        inst = self.add_inst(name=via.name,
                             mod=via,
                             offset=corrected_offset)
        # We don't model the logical connectivity of wires/paths
        self.connect_inst([])
        return inst

    def add_via_stack_center(self,
                             offset,
                             from_layer,
                             to_layer,
                             directions=None,
                             size=[1, 1],
                             implant_type=None,
                             well_type=None,
                             min_area=False):
        """
        Punch a stack of vias from a start layer to a target layer by the center.
        """

        if from_layer == to_layer:
            # In the case where we have no vias added, make sure that there is at least
            # a metal enclosure. This helps with center-line path routing.
            self.add_rect_center(layer=from_layer,
                                 offset=offset)
            return None

        intermediate_layers = self.get_metal_layers(from_layer, to_layer)

        via = None
        cur_layer = from_layer
        while cur_layer != to_layer:
            from_id = tech_layer_indices[cur_layer]
            to_id   = tech_layer_indices[to_layer]

            if from_id < to_id: # grow the stack up
                search_id = 0
                next_id = 2
            else: # grow the stack down
                search_id = 2
                next_id = 0

            curr_stack = next(filter(lambda stack: stack[search_id] == cur_layer, tech_layer_stacks), None)

            via = self.add_via_center(layers=curr_stack,
                                      size=size,
                                      offset=offset,
                                      directions=directions,
                                      implant_type=implant_type,
                                      well_type=well_type)

            # Only add the enclosure if we are in an intermediate layer
            # or we are forced to
            if min_area or cur_layer in intermediate_layers:
                self.add_min_area_rect_center(cur_layer,
                                              offset,
                                              via.mod.first_layer_width,
                                              via.mod.first_layer_height)

            cur_layer = curr_stack[next_id]

        return via

    def add_min_area_rect_center(self,
                                 layer,
                                 offset,
                                 width=None,
                                 height=None):
        """
        Add a minimum area retcangle at the given point.
        Either width or height should be fixed.
        """
        min_area = drc("minarea_{}".format(layer))
        if min_area == 0:
            return

        min_width = drc("minwidth_{}".format(layer))

        if preferred_directions[layer] == "V":
            new_height = ceil(max(min_area / width, min_width))
            new_width = width
        else:
            new_width = ceil(max(min_area / height, min_width))
            new_height = height
        debug.check(min_area <= round_to_grid(new_height*new_width), "Min area violated.")

        self.add_rect_center(layer=layer,
                             offset=offset,
                             width=new_width,
                             height=new_height)

    def add_ptx(self, offset, mirror="R0", rotate=0, width=1, mults=1, tx_type="nmos"):
        """Adds a ptx module to the design."""
        from openram.modules import ptx
        mos = ptx(width=width,
                  mults=mults,
                  tx_type=tx_type)
        inst = self.add_inst(name=mos.name,
                             mod=mos,
                             offset=offset,
                             mirror=mirror,
                             rotate=rotate)
        return inst

    def gds_read(self):
        """Reads a GDSII file in the library and checks if it exists
           Otherwise, start a new layout for dynamic generation."""

        if OPTS.netlist_only:
            self.gds = None
            return

        # open the gds file if it exists or else create a blank layout
        if os.path.isfile(self.gds_file):
            debug.info(3, "opening {}".format(self.gds_file))
            self.gds = gdsMill.VlsiLayout(units=GDS["unit"])
            reader = gdsMill.Gds2reader(self.gds)
            reader.loadFromFile(self.gds_file, special_purposes)
        else:
            debug.info(3, "Creating layout structure {}".format(self.name))
            self.gds = gdsMill.VlsiLayout(name=self.name, units=GDS["unit"])

    def print_gds(self, gds_file=None):
        """Print the gds file (not the vlsi class) to the terminal """
        if not gds_file:
            gds_file = self.gds_file
        debug.info(4, "Printing {}".format(gds_file))
        arrayCellLayout = gdsMill.VlsiLayout(units=GDS["unit"])
        reader = gdsMill.Gds2reader(arrayCellLayout, debugToTerminal=1)
        reader.loadFromFile(gds_file, special_purposes)

    def clear_visited(self):
        """ Recursively clear the visited flag """
        self.visited = []

    def gds_write_file(self, gds_layout):
        """Recursive GDS write function"""
        # Visited means that we already prepared self.gds for this subtree
        if self.name in self.visited:
            return
        for i in self.insts:
            i.gds_write_file(gds_layout)
        for i in self.objs:
            i.gds_write_file(gds_layout)
        for pin_name in self.pin_map.keys():
            for pin in self.pin_map[pin_name]:
                pin.gds_write_file(gds_layout)

        # If it's not a premade cell
        # and we didn't add our own boundary,
        # we should add a boundary just for DRC in some technologies
        if not self.is_library_cell and not self.bounding_box:
            # If there is a boundary layer, and we didn't create one, add one.
            boundary_layers = []
            if "boundary" in tech_layer.keys():
                boundary_layers.append("boundary")
            if "stdc" in tech_layer.keys():
                boundary_layers.append("stdc")
            boundary = [self.find_lowest_coords(),
                        self.find_highest_coords()]
            debug.check(boundary[0] and boundary[1], "No shapes to make a boundary.")

            height = boundary[1][1] - boundary[0][1]
            width = boundary[1][0] - boundary[0][0]

            for boundary_layer in boundary_layers:
                (layer_number, layer_purpose) = tech_layer[boundary_layer]
                gds_layout.addBox(layerNumber=layer_number,
                                  purposeNumber=layer_purpose,
                                  offsetInMicrons=boundary[0],
                                  width=width,
                                  height=height,
                                  center=False)
                debug.info(4, "Adding {0} boundary {1}".format(self.name, boundary))

        self.visited.append(self.name)

    def gds_write(self, gds_name):
        """Write the entire gds of the object to the file."""
        debug.info(3, "Writing to {}".format(gds_name))

        # If we already wrote a GDS, we need to reset and traverse it again in
        # case we made changes.
        if not self.is_library_cell and self.visited:
            debug.info(3, "Creating layout structure {}".format(self.name))
            self.gds = gdsMill.VlsiLayout(name=self.name, units=GDS["unit"])

        writer = gdsMill.Gds2writer(self.gds)
        # MRG: 3/2/18 We don't want to clear the visited flag since
        # this would result in duplicates of all instances being placed in self.gds
        # which may have been previously processed!
        # MRG: 10/4/18 We need to clear if we make changes and write a second GDS!
        self.clear_visited()

        # recursively create all the remaining objects
        self.gds_write_file(self.gds)

        # populates the xyTree data structure for gds
        # self.gds.prepareForWrite()
        writer.writeToFile(gds_name)
        debug.info(3, "Done writing to {}".format(gds_name))

    def get_boundary(self):
        """ Return the lower-left and upper-right coordinates of boundary """
        # This assumes nothing spans outside of the width and height!
        return [vector(0, 0), vector(self.width, self.height)]
        #return [self.find_lowest_coords(), self.find_highest_coords()]

    def get_blockages(self, layer, top_level=False):
        """
        Write all of the obstacles in the current (and children)
        modules to the lef file.
        Do not write the pins since they aren't obstructions.
        """
        if type(layer) == str:
            lpp = tech_layer[layer]
        else:
            lpp = layer

        blockages = []
        for i in self.objs:
            blockages += i.get_blockages(lpp)
        for i in self.insts:
            blockages += i.get_blockages(lpp)
        # Must add pin blockages to non-top cells
        if not top_level:
            blockages += self.get_pin_blockages(lpp)
        return blockages

    def get_pin_blockages(self, lpp):
        """ Return the pin shapes as blockages for non-top-level blocks. """
        # FIXME: We don't have a body contact in ptx, so just ignore it for now
        import copy
        # FIXME: this may not work now that self.pins is a dict as defined in hierarchy_spice
        pin_names = copy.deepcopy(self.pins)
        if self.name.startswith("pmos") or self.name.startswith("nmos"):
            pin_names.remove("B")

        blockages = []
        for pin_name in pin_names:
            pin_list = self.get_pins(pin_name)
            for pin in pin_list:
                if pin.same_lpp(pin.lpp, lpp):
                    blockages += [pin.rect]

        return blockages

    def create_horizontal_pin_bus(self, layer, offset, names, length, pitch=None):
        """ Create a horizontal bus of pins. """
        return self.create_bus(layer,
                               offset,
                               names,
                               length,
                               vertical=False,
                               make_pins=True,
                               pitch=pitch)

    def create_vertical_pin_bus(self, layer, offset, names, length, pitch=None):
        """ Create a horizontal bus of pins. """
        return self.create_bus(layer,
                               offset,
                               names,
                               length,
                               vertical=True,
                               make_pins=True,
                               pitch=pitch)

    def create_vertical_bus(self, layer, offset, names, length, pitch=None):
        """ Create a horizontal bus. """
        return self.create_bus(layer,
                               offset,
                               names,
                               length,
                               vertical=True,
                               make_pins=False,
                               pitch=pitch)

    def create_horizontal_bus(self, layer, offset, names, length, pitch=None):
        """ Create a horizontal bus. """
        return self.create_bus(layer,
                               offset,
                               names,
                               length,
                               vertical=False,
                               make_pins=False,
                               pitch=pitch)

    def create_bus(self, layer, offset, names, length, vertical, make_pins, pitch=None):
        """
        Create a horizontal or vertical bus. It can be either just rectangles, or actual
        layout pins. It returns an map of line center line positions indexed by name.
        The other coordinate is a 0 since the bus provides a range.
        TODO: combine with channel router.
        """

        # half minwidth so we can return the center line offsets
        half_minwidth = 0.5 * drc["minwidth_{}".format(layer)]
        if not pitch:
            pitch = getattr(self, "{}_pitch".format(layer))

        pins = {}
        if vertical:
            for i in range(len(names)):
                line_offset = offset + vector(i * pitch,
                                              0)
                if make_pins:
                    new_pin = self.add_layout_pin(text=names[i],
                                                  layer=layer,
                                                  offset=line_offset,
                                                  height=length)
                else:
                    rect = self.add_rect(layer=layer,
                                         offset=line_offset,
                                         height=length)
                    new_pin = pin_layout(names[i],
                                         [rect.ll(), rect.ur()],
                                         layer)

                pins[names[i]] = new_pin
        else:
            for i in range(len(names)):
                line_offset = offset + vector(0,
                                              i * pitch + half_minwidth)
                if make_pins:
                    new_pin = self.add_layout_pin(text=names[i],
                                                  layer=layer,
                                                  offset=line_offset,
                                                  width=length)
                else:
                    rect = self.add_rect(layer=layer,
                                         offset=line_offset,
                                         width=length)
                    new_pin = pin_layout(names[i],
                                         [rect.ll(), rect.ur()],
                                         layer)

                pins[names[i]] = new_pin

        return pins

    def connect_horizontal_bus(self, mapping, inst, bus_pins,
                               layer_stack=("m1", "via1", "m2")):
        """ Horizontal version of connect_bus. """
        self.connect_bus(mapping, inst, bus_pins, layer_stack, True)

    def connect_vertical_bus(self, mapping, inst, bus_pins,
                             layer_stack=("m1", "via1", "m2")):
        """ Vertical version of connect_bus. """
        self.connect_bus(mapping, inst, bus_pins, layer_stack, False)

    def connect_bus(self, mapping, inst, bus_pins, layer_stack, horizontal):
        """
        Connect a mapping of pin -> name for a bus. This could be
        replaced with a channel router in the future.
        NOTE: This has only really been tested with point-to-point
        connections (not multiple pins on a net).
        """
        (horizontal_layer, via_layer, vertical_layer) = layer_stack
        if horizontal:
            route_layer = vertical_layer
            bus_layer = horizontal_layer
        else:
            route_layer = horizontal_layer
            bus_layer = vertical_layer

        for (pin_name, bus_name) in mapping:
            pin = inst.get_pin(pin_name)
            pin_pos = pin.center()
            bus_pos = bus_pins[bus_name].center()

            if horizontal:
                # up/down then left/right
                mid_pos = vector(pin_pos.x, bus_pos.y)
            else:
                # left/right then up/down
                mid_pos = vector(bus_pos.x, pin_pos.y)

            # Don't widen short wires because pin_pos and mid_pos could be really close
            self.add_wire(layer_stack,
                          [bus_pos, mid_pos, pin_pos],
                          widen_short_wires=False)

            # Connect to the pin on the instances with a via if it is
            # not on the right layer
            if pin.layer != route_layer:
                self.add_via_stack_center(from_layer=pin.layer,
                                          to_layer=route_layer,
                                          offset=pin_pos)
            # FIXME: output pins tend to not be rotate,
            # but supply pins are. Make consistent?

            # We only need a via if they happened to align perfectly
            # so the add_wire didn't add a via
            if (horizontal and bus_pos.y == pin_pos.y) or (not horizontal and bus_pos.x == pin_pos.x):
                self.add_via_stack_center(from_layer=route_layer,
                                          to_layer=bus_layer,
                                          offset=bus_pos)

    def connect_vbus(self, src_pin, dest_pin, hlayer="m3", vlayer="m2"):
        """
        Helper routine to connect an instance to a vertical bus.
        Routes horizontal then vertical L shape.
        """

        if src_pin.cx()<dest_pin.cx():
            in_pos = src_pin.rc()
        else:
            in_pos = src_pin.lc()
        if src_pin.cy() < dest_pin.cy():
            out_pos = dest_pin.bc()
        else:
            out_pos = dest_pin.uc()

        # move horizontal first on layer stack
        mid_pos = vector(out_pos.x, in_pos.y)
        self.add_via_stack_center(from_layer=src_pin.layer,
                                  to_layer=hlayer,
                                  offset=in_pos)
        self.add_path(hlayer, [in_pos, mid_pos])
        self.add_via_stack_center(from_layer=hlayer,
                                  to_layer=vlayer,
                                  offset=mid_pos)
        self.add_path(vlayer, [mid_pos, out_pos])
        self.add_via_stack_center(from_layer=vlayer,
                                  to_layer=dest_pin.layer,
                                  offset=out_pos)

    def connect_hbus(self, src_pin, dest_pin, hlayer="m3", vlayer="m2"):
        """
        Helper routine to connect an instance to a horizontal bus.
        Routes horizontal then vertical L shape.
        """

        if src_pin.cx()<dest_pin.cx():
            in_pos = src_pin.rc()
        else:
            in_pos = src_pin.lc()
        if src_pin.cy() < dest_pin.cy():
            out_pos = dest_pin.lc()
        else:
            out_pos = dest_pin.rc()

        # move horizontal first
        mid_pos = vector(out_pos.x, in_pos.y)
        self.add_via_stack_center(from_layer=src_pin.layer,
                                  to_layer=hlayer,
                                  offset=in_pos)
        self.add_path(hlayer, [in_pos, mid_pos])
        self.add_via_stack_center(from_layer=hlayer,
                                  to_layer=vlayer,
                                  offset=mid_pos)
        self.add_path(vlayer, [mid_pos, out_pos])
        self.add_via_stack_center(from_layer=vlayer,
                                  to_layer=dest_pin.layer,
                                  offset=out_pos)

    def create_vertical_channel_route(self, netlist, offset, layer_stack, directions=None):
        """
        Wrapper to create a vertical channel route
        """
        from .channel_route import channel_route
        cr = channel_route(netlist, offset, layer_stack, directions, vertical=True, parent=self)
        # This causes problem in magic since it sometimes cannot extract connectivity of isntances
        # with no active devices.
        # self.add_inst(cr.name, cr)
        # self.connect_inst([])
        self.add_flat_inst(cr.name, cr)

    def create_horizontal_channel_route(self, netlist, offset, layer_stack, directions=None):
        """
        Wrapper to create a horizontal channel route
        """
        from .channel_route import channel_route
        cr = channel_route(netlist, offset, layer_stack, directions, vertical=False, parent=self)
        # This causes problem in magic since it sometimes cannot extract connectivity of isntances
        # with no active devices.
        # self.add_inst(cr.name, cr)
        # self.connect_inst([])
        self.add_flat_inst(cr.name, cr)

    def add_boundary(self, ll=vector(0, 0), ur=None):
        """ Add boundary for debugging dimensions """
        if OPTS.netlist_only:
            return

        boundary_layers = []
        if "stdc" in tech_layer.keys():
            boundary_layers.append("stdc")
        if "boundary" in tech_layer.keys():
            boundary_layers.append("boundary")
        # Save the last one as self.bounding_box
        for boundary_layer in boundary_layers:
            if not ur:
                self.bounding_box = self.add_rect(layer=boundary_layer,
                                                  offset=ll,
                                                  height=self.height,
                                                  width=self.width)
            else:
                self.bounding_box = self.add_rect(layer=boundary_layer,
                                                  offset=ll,
                                                  height=ur.y - ll.y,
                                                  width=ur.x - ll.x)

        self.bbox = [self.bounding_box.ll(), self.bounding_box.ur()]

    def get_bbox(self, side="all", margin=0):
        """
        Get the bounding box from the GDS
        """
        gds_filename = OPTS.openram_temp + "temp.gds"
        # If didn't specify a gds blockage file, write it out to read the gds
        # This isn't efficient, but easy for now
        # Load the gds file and read in all the shapes
        self.gds_write(gds_filename)
        layout = gdsMill.VlsiLayout(units=GDS["unit"])
        reader = gdsMill.Gds2reader(layout)
        reader.loadFromFile(gds_filename)
        top_name = layout.rootStructureName

        if not self.bbox:
            # The boundary will determine the limits to the size
            # of the routing grid
            boundary = layout.measureBoundary(top_name)
            # These must be un-indexed to get rid of the matrix type
            ll = vector(boundary[0][0], boundary[0][1])
            ur = vector(boundary[1][0], boundary[1][1])
        else:
            ll, ur = self.bbox

        ll_offset = vector(0, 0)
        ur_offset = vector(0, 0)
        if side in ["ring", "top", "all"]:
            ur_offset += vector(0, margin)
        if side in ["ring", "bottom", "all"]:
            ll_offset += vector(0, margin)
        if side in ["ring", "left", "all"]:
            ll_offset += vector(margin, 0)
        if side in ["ring", "right", "all"]:
            ur_offset += vector(margin, 0)
        bbox = (ll - ll_offset, ur + ur_offset)
        size = ur - ll
        debug.info(1, "Size: {0} x {1} with perimeter margin {2}".format(size.x,
                                                                                               size.y,
                                                                                               margin))

        return bbox

    def add_enclosure(self, insts, layer="nwell", extend=0, leftx=None, rightx=None, topy=None, boty=None):
        """
        Add a layer that surrounds the given instances. Useful
        for creating wells, for example. Doesn't check for minimum widths or
        spacings. Extra arg can force a dimension to one side left/right top/bot.
        """

        if leftx != None:
            xmin = leftx
        else:
            xmin = insts[0].lx()
            for inst in insts:
                xmin = min(xmin, inst.lx())
            xmin = xmin - extend
        if boty != None:
            ymin = boty
        else:
            ymin = insts[0].by()
            for inst in insts:
                ymin = min(ymin, inst.by())
            ymin = ymin - extend
        if rightx != None:
            xmax = rightx
        else:
            xmax = insts[0].rx()
            for inst in insts:
                xmax = max(xmax, inst.rx())
            xmax = xmax + extend
        if topy != None:
            ymax = topy
        else:
            ymax = insts[0].uy()
            for inst in insts:
                ymax = max(ymax, inst.uy())
            ymax = ymax + extend

        rect = self.add_rect(layer=layer,
                             offset=vector(xmin, ymin),
                             width=xmax - xmin,
                             height=ymax - ymin)
        return rect

    def copy_power_pins(self, inst, name, add_vias=True, new_name=""):
        """
        This will copy a power pin if it is on the lowest power_grid layer.
        If it is on M1, it will add a power via too.
        """
        pins = inst.get_pins(name)
        for pin in pins:
            if new_name == "":
                new_name = pin.name
            if pin.layer in self.pwr_grid_layers:
                self.add_layout_pin(new_name,
                                    pin.layer,
                                    pin.ll(),
                                    pin.width(),
                                    pin.height())

            elif add_vias:
                self.copy_power_pin(pin, new_name=new_name)

    def add_io_pin(self, instance, pin_name, new_name, start_layer=None, directions=None):
        """
        Add a signle input or output pin up to metal 3.
        """
        pin = instance.get_pin(pin_name)

        if not start_layer:
            start_layer = pin.layer

        # Just use the power pin function for now to save code
        self.add_power_pin(new_name, pin.center(), start_layer=start_layer, directions=directions)

    def add_power_pin(self, name, loc, directions=None, start_layer="m1"):
        # Hack for min area
        if OPTS.tech_name == "sky130":
            min_area = drc["minarea_{}".format(self.pwr_grid_layers[1])]
            width = round_to_grid(sqrt(min_area))
            height = round_to_grid(min_area / width)
        else:
            width = None
            height = None

        pin = None
        if start_layer in self.pwr_grid_layers:
            pin = self.add_layout_pin_rect_center(text=name,
                                                   layer=start_layer,
                                                   offset=loc,
                                                   width=width,
                                                   height=height)
        else:
            via = self.add_via_stack_center(from_layer=start_layer,
                                            to_layer=self.pwr_grid_layers[0],
                                            offset=loc,
                                            directions=directions)

            if not width:
                width = via.width
            if not height:
                height = via.height
            pin = self.add_layout_pin_rect_center(text=name,
                                                  layer=self.pwr_grid_layers[0],
                                                  offset=loc,
                                                  width=width,
                                                  height=height)

        return pin

    def copy_power_pin(self, pin, loc=None, directions=None, new_name=""):
        """
        Add a single power pin from the lowest power_grid layer down to M1 (or li) at
        the given center location. The starting layer is specified to determine
        which vias are needed.
        """

        if new_name == "":
                new_name = pin.name
        if not loc:
            loc = pin.center()

        # Hack for min area
        if OPTS.tech_name == "sky130":
            min_area = drc["minarea_{}".format(self.pwr_grid_layers[1])]
            width = round_to_grid(sqrt(min_area))
            height = round_to_grid(min_area / width)
        else:
            width = None
            height = None

        if pin.layer in self.pwr_grid_layers:
            self.add_layout_pin_rect_center(text=new_name,
                                            layer=pin.layer,
                                            offset=loc,
                                            width=width,
                                            height=height)
        else:
            via = self.add_via_stack_center(from_layer=pin.layer,
                                            to_layer=self.pwr_grid_layers[0],
                                            offset=loc,
                                            directions=directions)

            if not width:
                width = via.width
            if not height:
                height = via.height
            self.add_layout_pin_rect_center(text=new_name,
                                            layer=self.pwr_grid_layers[0],
                                            offset=loc,
                                            width=width,
                                            height=height)

    def add_perimeter_pin(self, name, pin, side, bbox):
        """
        Add a pin along the perimeter side specified by the bbox with
        the given name and layer from the pin starting location.
        """
        (ll, ur) = bbox
        left = ll.x
        bottom = ll.y
        right = ur.x
        top = ur.y

        pin_loc = pin.center()
        if side == "left":
            peri_pin_loc = vector(left, pin_loc.y)
            layer = "m3"
        elif side == "right":
            layer = "m3"
            peri_pin_loc = vector(right, pin_loc.y)
        elif side == "top":
            layer = "m4"
            peri_pin_loc = vector(pin_loc.x, top)
        elif side == "bottom":
            layer = "m4"
            peri_pin_loc = vector(pin_loc.x, bottom)

        self.add_via_stack_center(from_layer=pin.layer,
                                  to_layer=layer,
                                  offset=pin_loc)

        self.add_path(layer,
                      [pin_loc, peri_pin_loc])

        return self.add_layout_pin_rect_center(text=name,
                                               layer=layer,
                                               offset=peri_pin_loc)

    def add_dnwell(self, bbox=None, inflate=1):
        """ Create a dnwell, along with nwell moat at border. """

        if "dnwell" not in tech_layer:
            return

        if not bbox:
            bbox =  [self.find_lowest_coords(),
                     self.find_highest_coords()]

        # Find the corners
        [ll, ur] = bbox

        # Possibly inflate the bbox
        nwell_offset = vector(2 * self.nwell_width, 2 * self.nwell_width)
        ll -= nwell_offset.scale(inflate, inflate)
        ur += nwell_offset.scale(inflate, inflate)

        # Other corners
        ul = vector(ll.x, ur.y)
        lr = vector(ur.x, ll.y)

        # Add the dnwell
        self.add_rect("dnwell",
                      offset=ll,
                      height=ur.y - ll.y,
                      width=ur.x - ll.x)

        # Add the moat
        self.add_path("nwell", [ll, lr, ur, ul, ll - vector(0, 0.5 * self.nwell_width)])

        # Add the taps
        layer_stack = self.active_stack
        tap_spacing = 2
        nwell_offset = vector(self.nwell_width, self.nwell_width)

        # Every nth tap is connected to gnd
        period = 5

        # BOTTOM
        count = 0
        loc = ll + nwell_offset.scale(tap_spacing, 0)
        end_loc = lr - nwell_offset.scale(tap_spacing, 0)
        while loc.x < end_loc.x:
            self.add_via_center(layers=layer_stack,
                                offset=loc,
                                implant_type="n",
                                well_type="n")
            if count % period:
                self.add_via_stack_center(from_layer="li",
                                          to_layer="m1",
                                          offset=loc)
            else:
                self.add_power_pin(name="vdd",
                                   loc=loc,
                                   start_layer="li")
            count += 1
            loc += nwell_offset.scale(tap_spacing, 0)

        # TOP
        count = 0
        loc = ul + nwell_offset.scale(tap_spacing, 0)
        end_loc = ur - nwell_offset.scale(tap_spacing, 0)
        while loc.x < end_loc.x:
            self.add_via_center(layers=layer_stack,
                                offset=loc,
                                implant_type="n",
                                well_type="n")
            if count % period:
                self.add_via_stack_center(from_layer="li",
                                          to_layer="m1",
                                          offset=loc)
            else:
                self.add_power_pin(name="vdd",
                                   loc=loc,
                                   start_layer="li")
            count += 1
            loc += nwell_offset.scale(tap_spacing, 0)

        # LEFT
        count = 0
        loc = ll + nwell_offset.scale(0, tap_spacing)
        end_loc = ul - nwell_offset.scale(0, tap_spacing)
        while loc.y < end_loc.y:
            self.add_via_center(layers=layer_stack,
                                offset=loc,
                                implant_type="n",
                                well_type="n")
            if count % period:
                self.add_via_stack_center(from_layer="li",
                                          to_layer="m2",
                                          offset=loc)
            else:
                self.add_power_pin(name="vdd",
                                   loc=loc,
                                   start_layer="li")
            count += 1
            loc += nwell_offset.scale(0, tap_spacing)

        # RIGHT
        count = 0
        loc = lr + nwell_offset.scale(0, tap_spacing)
        end_loc = ur - nwell_offset.scale(0, tap_spacing)
        while loc.y < end_loc.y:
            self.add_via_center(layers=layer_stack,
                                offset=loc,
                                implant_type="n",
                                well_type="n")
            if count % period:
                self.add_via_stack_center(from_layer="li",
                                          to_layer="m2",
                                          offset=loc)
            else:
                self.add_power_pin(name="vdd",
                                   loc=loc,
                                   start_layer="li")
            count += 1
            loc += nwell_offset.scale(0, tap_spacing)

        # Add the gnd ring
        self.add_ring([ll, ur])

    def add_ring(self, bbox=None, width_mult=8, offset=0):
        """
        Add a ring around the bbox
        """
        # Ring size/space/pitch
        wire_width = self.m2_width * width_mult
        half_width = 0.5 * wire_width
        wire_space = self.m2_space
        wire_pitch = wire_width + wire_space

        # Find the corners
        if not bbox:
            bbox =  [self.find_lowest_coords(),
                     self.find_highest_coords()]

        [ll, ur] = bbox
        ul = vector(ll.x, ur.y)
        lr = vector(ur.x, ll.y)
        ll += vector(-offset * wire_pitch,
                     -offset * wire_pitch)
        lr += vector(offset * wire_pitch,
                     -offset * wire_pitch)
        ur += vector(offset * wire_pitch,
                     offset * wire_pitch)
        ul += vector(-offset * wire_pitch,
                     offset * wire_pitch)

        half_offset = vector(half_width, half_width)
        self.add_path("m1", [ll - half_offset.scale(1, 0), lr + half_offset.scale(1, 0)], width=wire_width)
        self.add_path("m1", [ul - half_offset.scale(1, 0), ur + half_offset.scale(1, 0)], width=wire_width)
        self.add_path("m2", [ll - half_offset.scale(0, 1), ul + half_offset.scale(0, 1)], width=wire_width)
        self.add_path("m2", [lr - half_offset.scale(0, 1), ur + half_offset.scale(0, 1)], width=wire_width)

        # Find the number of vias for this pitch
        supply_vias = 1
        while True:
            c = factory.create(module_type="contact",
                               layer_stack=self.m1_stack,
                               dimensions=(supply_vias, supply_vias))
            if c.second_layer_width < wire_width and c.second_layer_height < wire_width:
                supply_vias += 1
            else:
                supply_vias -= 1
                break

        via_points = [ll, lr, ur, ul]
        for pt in via_points:
            self.add_via_center(layers=self.m1_stack,
                                offset=pt,
                                size=(supply_vias,
                                      supply_vias))

    def add_power_ring(self):
        """
        Create vdd and gnd power rings around an area of the bounding box
        argument. Must have a supply_rail_width and supply_rail_pitch
        defined as a member variable.  Defines local variables of the
        left/right/top/bottom vdd/gnd center offsets for use in other
        modules..
        """

        [ll, ur] = self.bbox

        supply_rail_spacing = self.supply_rail_pitch - self.supply_rail_width
        height = (ur.y - ll.y) + 3 * self.supply_rail_pitch - supply_rail_spacing
        width = (ur.x - ll.x) + 3 * self.supply_rail_pitch - supply_rail_spacing

        # LEFT vertical rails
        offset = ll + vector(-2 * self.supply_rail_pitch,
                             -2 * self.supply_rail_pitch)
        left_gnd_pin = self.add_layout_pin(text="gnd",
                                           layer="m2",
                                           offset=offset,
                                           width=self.supply_rail_width,
                                           height=height)

        offset = ll + vector(-1 * self.supply_rail_pitch,
                             -1 * self.supply_rail_pitch)
        left_vdd_pin = self.add_layout_pin(text="vdd",
                                           layer="m2",
                                           offset=offset,
                                           width=self.supply_rail_width,
                                           height=height)

        # RIGHT vertical rails
        offset = vector(ur.x, ll.y) + vector(0, -2 * self.supply_rail_pitch)
        right_gnd_pin = self.add_layout_pin(text="gnd",
                                            layer="m2",
                                            offset=offset,
                                            width=self.supply_rail_width,
                                            height=height)

        offset = vector(ur.x, ll.y) + vector(self.supply_rail_pitch,
                                            -1 * self.supply_rail_pitch)
        right_vdd_pin = self.add_layout_pin(text="vdd",
                                            layer="m2",
                                            offset=offset,
                                            width=self.supply_rail_width,
                                            height=height)

        # BOTTOM horizontal rails
        offset = ll + vector(-2 * self.supply_rail_pitch,
                             -2 * self.supply_rail_pitch)
        bottom_gnd_pin = self.add_layout_pin(text="gnd",
                                             layer="m1",
                                             offset=offset,
                                             width=width,
                                             height=self.supply_rail_width)

        offset = ll + vector(-1 * self.supply_rail_pitch,
                             -1 * self.supply_rail_pitch)
        bottom_vdd_pin = self.add_layout_pin(text="vdd",
                                             layer="m1",
                                             offset=offset,
                                             width=width,
                                             height=self.supply_rail_width)

        # TOP horizontal rails
        offset = vector(ll.x, ur.y) + vector(-2 * self.supply_rail_pitch,
                                             0)
        top_gnd_pin = self.add_layout_pin(text="gnd",
                                          layer="m1",
                                          offset=offset,
                                          width=width,
                                          height=self.supply_rail_width)

        offset = vector(ll.x, ur.y) + vector(-1 * self.supply_rail_pitch,
                                             self.supply_rail_pitch)
        top_vdd_pin = self.add_layout_pin(text="vdd",
                                          layer="m1",
                                          offset=offset,
                                          width=width,
                                          height=self.supply_rail_width)

        # Remember these for connecting things in the design
        self.left_gnd_x_center = left_gnd_pin.cx()
        self.left_vdd_x_center = left_vdd_pin.cx()
        self.right_gnd_x_center = right_gnd_pin.cx()
        self.right_vdd_x_center = right_vdd_pin.cx()

        self.bottom_gnd_y_center = bottom_gnd_pin.cy()
        self.bottom_vdd_y_center = bottom_vdd_pin.cy()
        self.top_gnd_y_center = top_gnd_pin.cy()
        self.top_vdd_y_center = top_vdd_pin.cy()

        # Find the number of vias for this pitch
        self.supply_vias = 1
        while True:
            c = factory.create(module_type="contact",
                               layer_stack=self.m1_stack,
                               dimensions=(self.supply_vias, self.supply_vias))
            if c.second_layer_width < self.supply_rail_width and c.second_layer_height < self.supply_rail_width:
                self.supply_vias += 1
            else:
                self.supply_vias -= 1
                break

        via_points = [vector(self.left_gnd_x_center, self.bottom_gnd_y_center),
                      vector(self.left_gnd_x_center, self.top_gnd_y_center),
                      vector(self.right_gnd_x_center, self.bottom_gnd_y_center),
                      vector(self.right_gnd_x_center, self.top_gnd_y_center),
                      vector(self.left_vdd_x_center, self.bottom_vdd_y_center),
                      vector(self.left_vdd_x_center, self.top_vdd_y_center),
                      vector(self.right_vdd_x_center, self.bottom_vdd_y_center),
                      vector(self.right_vdd_x_center, self.top_vdd_y_center)]

        for pt in via_points:
            self.add_via_center(layers=self.m1_stack,
                                offset=pt,
                                size=(self.supply_vias,
                                      self.supply_vias))

    def pdf_write(self, pdf_name):
        """
        Display the layout to a PDF file.
        """
        debug.error("NOTE: Currently does not work (Needs further research)")
        # self.pdf_name = self.name + ".pdf"
        debug.info(0, "Writing to {}".format(pdf_name))
        pdf = gdsMill.pdfLayout(self.gds)

        return
        pdf.layerColors[self.gds.layerNumbersInUse[0]] = "#219E1C"
        pdf.layerColors[self.gds.layerNumbersInUse[1]] = "#271C9E"
        pdf.layerColors[self.gds.layerNumbersInUse[2]] = "#CC54C8"
        pdf.layerColors[self.gds.layerNumbersInUse[3]] = "#E9C514"
        pdf.layerColors[self.gds.layerNumbersInUse[4]] = "#856F00"
        pdf.layerColors[self.gds.layerNumbersInUse[5]] = "#BD1444"
        pdf.layerColors[self.gds.layerNumbersInUse[6]] = "#FD1444"
        pdf.layerColors[self.gds.layerNumbersInUse[7]] = "#FD1414"

        pdf.setScale(500)
        pdf.drawLayout()
        pdf.writeToFile(pdf_name)



    def print_attr(self):
        """Prints a list of attributes for the current layout object"""
        debug.info(0,
                   "|==============================================================================|")
        debug.info(0,
                   "|=========      LIST OF OBJECTS (Rects) FOR: " + self.name)
        debug.info(0,
                   "|==============================================================================|")
        for obj in self.objs:
            debug.info(0, "layer={0} : offset={1} : size={2}".format(obj.layerNumber,
                                                                     obj.offset,
                                                                     obj.size))

        debug.info(0,
                   "|==============================================================================|")
        debug.info(0,
                   "|=========      LIST OF INSTANCES FOR: " + self.name)
        debug.info(0,
                   "|==============================================================================|")
        for inst in self.insts:
            debug.info(0, "name={0} : mod={1} : offset={2}".format(inst.name,
                                                                   inst.mod.name,
                                                                   inst.offset))
